use loco_rs::prelude::*;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub use super::_entities::order_items::{self, ActiveModel, Entity, Model};

#[derive(Debug, Deserialize, Serialize)]
pub struct CreateParams {
    pub order_id: String,
    pub product_id: String,
    pub order_number: String,
    pub product_sku: String,
    pub variant_label: String,
    pub name: String,
    pub image: String,
    pub quantity: i32,
    pub price: Decimal,
}

#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(length(min = 1, message = "Order ID must not be empty."))]
    pub order_id: String,
    #[validate(length(min = 1, message = "Product ID must not be empty."))]
    pub product_id: String,
    #[validate(length(min = 1, message = "Order number must not be empty."))]
    pub order_number: String,
    #[validate(length(min = 1, message = "Product SKU must not be empty."))]
    pub product_sku: String,
    #[validate(length(min = 1, message = "Name must not be empty."))]
    pub name: String,
}

impl Validatable for ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            order_id: self.order_id.as_ref().to_owned(),
            product_id: self.product_id.as_ref().to_owned(),
            order_number: self.order_number.as_ref().to_owned(),
            product_sku: self.product_sku.as_ref().to_owned(),
            name: self.name.as_ref().to_owned(),
        })
    }
}

#[async_trait::async_trait]
impl ActiveModelBehavior for super::_entities::order_items::ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        self.validate()?;
        if insert {
            let mut this = self;
            this.id = ActiveValue::Set(Uuid::new_v4().to_string());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

impl Model {
    /// finds an order item by the provided id
    ///
    /// # Errors
    ///
    /// When could not find order item or DB query error
    pub async fn find_by_id(db: &DatabaseConnection, id: &str) -> ModelResult<Self> {
        let order_item = order_items::Entity::find()
            .filter(
                model::query::condition()
                    .eq(order_items::Column::Id, id)
                    .build(),
            )
            .one(db)
            .await?;
        order_item.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds order items by the provided order id
    ///
    /// # Errors
    ///
    /// When could not find order items or DB query error
    pub async fn find_by_order_id(
        db: &DatabaseConnection,
        order_id: &str,
    ) -> ModelResult<Vec<Self>> {
        let order_items = order_items::Entity::find()
            .filter(
                model::query::condition()
                    .eq(order_items::Column::OrderId, order_id)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(order_items)
    }

    /// finds order items by the provided order number
    ///
    /// # Errors
    ///
    /// When could not find order items or DB query error
    pub async fn find_by_order_number(
        db: &DatabaseConnection,
        order_number: &str,
    ) -> ModelResult<Vec<Self>> {
        let order_items = order_items::Entity::find()
            .filter(
                model::query::condition()
                    .eq(order_items::Column::OrderNumber, order_number)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(order_items)
    }

    /// Asynchronously creates an order item and saves it to the database.
    ///
    /// # Errors
    ///
    /// When could not save the order item into the DB
    pub async fn create(db: &DatabaseConnection, params: &CreateParams) -> ModelResult<Self> {
        let order_item = order_items::ActiveModel {
            order_id: ActiveValue::set(params.order_id.to_string()),
            product_id: ActiveValue::set(params.product_id.to_string()),
            order_number: ActiveValue::set(params.order_number.to_string()),
            product_sku: ActiveValue::set(params.product_sku.to_string()),
            variant_label: ActiveValue::set(params.variant_label.to_string()),
            name: ActiveValue::set(params.name.to_string()),
            image: ActiveValue::set(params.image.to_string()),
            quantity: ActiveValue::set(params.quantity),
            price: ActiveValue::set(params.price),
            ..Default::default()
        }
        .insert(db)
        .await?;

        Ok(order_item)
    }

    /// Soft deletes the order item
    ///
    /// # Errors
    ///
    /// When could not delete the order item from the DB
    pub async fn soft_delete(self, db: &DatabaseConnection) -> ModelResult<Self> {
        let mut model: order_items::ActiveModel = self.into();
        model.deleted_at = ActiveValue::set(Some(chrono::Utc::now().into()));
        Ok(model.update(db).await?)
    }
}

impl ActiveModel {
    /// Updates the order item's quantity
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_quantity(
        mut self,
        db: &DatabaseConnection,
        quantity: i32,
    ) -> ModelResult<Model> {
        self.quantity = ActiveValue::set(quantity);
        Ok(self.update(db).await?)
    }

    /// Updates the order item's price
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_price(
        mut self,
        db: &DatabaseConnection,
        price: Decimal,
    ) -> ModelResult<Model> {
        self.price = ActiveValue::set(price);
        Ok(self.update(db).await?)
    }
}
